﻿namespace NetTopologySuite.IO.EsriJSON
{

    public class EsriJsonReader
    {
        private readonly CoordinateSequenceFactory _coordinateSequenceFactory;
        private GeometryFactory _factory;


        public EsriJsonReader()
            : this(NtsGeometryServices.Instance.CreateGeometryFactory())
        {

        }

        public EsriJsonReader(GeometryFactory factory)
          : this(factory, factory.CoordinateSequenceFactory)
        {
        }


        public EsriJsonReader(GeometryFactory factory, CoordinateSequenceFactory coordinateSequenceFactory)
        {
            this._factory = NtsGeometryServices.Instance.CreateGeometryFactory(coordinateSequenceFactory);
            this._coordinateSequenceFactory = coordinateSequenceFactory;
        }


        public virtual Geometry Read(string esriJson)
        {
            Geometry geometry = null;
            if (string.IsNullOrWhiteSpace(esriJson))
            {
                throw new ArgumentException("Data is empty!", nameof(esriJson));
            }
            var JSON = esriJson.ToEsriJSON();
            if (JSON.x > 0)
            {
                // Point
                var x = JSON.x;
                var y = JSON.y;
                var z = JSON.z;
                var m = JSON.m;
                var readOrdinates = Ordinates.XY;
                if (z != 0 && m != 0)
                {
                    readOrdinates = Ordinates.XYZM;
                }
                else if (z != 0)
                {
                    readOrdinates = Ordinates.XYZ;
                }
                else if (m != 0)
                {
                    readOrdinates = Ordinates.XYM;
                }
                Coordinate res;
                switch (readOrdinates)
                {
                    case Ordinates.XY:
                        res = new Coordinate(x, y); break;
                    case Ordinates.XYZ:
                        res = new CoordinateZ(x, y, z); ; break;
                    case Ordinates.XYM:
                        res = new CoordinateM(x, y, m); break;
                    case Ordinates.XYZM:
                        res = new CoordinateZM(x, y, z, m); break;
                    default:
                        throw new InvalidOperationException($"Unknown ordinate read: {readOrdinates}");
                }
                geometry = _factory.CreatePoint(res);
            }
            else
            {
                var ordinates = Ordinates.XY;
                if (JSON.hasZ && JSON.hasM)
                {
                    ordinates = Ordinates.XYZM;
                }
                else if (JSON.hasZ)
                {
                    ordinates = Ordinates.XYZ;
                }
                else if (JSON.hasM)
                {
                    ordinates = Ordinates.XYM;
                }

                // MultiPoint
                if (JSON.points != null)
                {
                    var lstCoordinate = new List<Coordinate>();
                    foreach (var item in JSON.points)
                    {
                        lstCoordinate.Add(GetCoordinate(item, ordinates));
                    }
                    geometry = _factory.CreateMultiPoint(_coordinateSequenceFactory.Create(lstCoordinate.ToArray()));
                }
                else if (JSON.paths != null)
                {
                    var lstCoordinate = new List<Coordinate>();
                    var lstLineString = new List<LineString>();
                    foreach (var path in JSON.paths)
                    {
                        lstCoordinate = new List<Coordinate>();
                        foreach (var items in path)
                        {
                            lstCoordinate.Add(GetCoordinate(items, ordinates));
                        }
                        var lineString = _factory.CreateLineString(_coordinateSequenceFactory.Create(lstCoordinate.ToArray()));
                        lstLineString.Add(lineString);
                    }

                    if (lstLineString.Count == 1)
                    {
                        geometry = lstLineString.FirstOrDefault();
                    }
                    else
                    {
                        geometry = _factory.CreateMultiLineString(lstLineString.ToArray());
                    }
                }
                else if (JSON.rings != null)
                {
                    var lstCoordinate = new List<Coordinate>();
                    var lstPolygon = new List<Polygon>();
                    foreach (var path in JSON.rings)
                    {
                        lstCoordinate = new List<Coordinate>();
                        foreach (var items in path)
                        {
                            lstCoordinate.Add(GetCoordinate(items, ordinates));
                        }
                        var polygon = _factory.CreatePolygon(_coordinateSequenceFactory.Create(lstCoordinate.ToArray()));
                        lstPolygon.Add(polygon);
                    }
                    if (lstPolygon.Count == 1)
                    {
                        geometry = lstPolygon.FirstOrDefault();
                    }
                    else
                    {
                        geometry = _factory.CreateMultiPolygon(lstPolygon.ToArray());
                    }
                }
            }

            if (JSON.spatialReference != null && JSON.spatialReference.wkid > 0)
            {
                geometry.SRID = JSON.spatialReference.wkid;
            }
            return geometry;
        }

        private Coordinate GetCoordinate(List<double> vals, Ordinates ordinates)
        {
            Coordinate res;
            switch (ordinates)
            {
                case Ordinates.XY:
                    res = new Coordinate(vals[0], vals[1]); break;
                case Ordinates.XYZ:
                    res = new CoordinateZ(vals[0], vals[1], vals[2]); ; break;
                case Ordinates.XYM:
                    res = new CoordinateM(vals[0], vals[1], vals[2]); break;
                case Ordinates.XYZM:
                    res = new CoordinateZM(vals[0], vals[1], vals[2], vals[3]); break;
                default:
                    throw new InvalidOperationException($"Unknown ordinate read: {ordinates}");
            }
            return res;
        }
    }
}
